大家好,我是 Yubin
這篇文章跟大家介紹在開發後端網路應用程式的時候,非常重要的環節,快取 (Cache)。
Cache 快取是什麼?
想像一下,你在第一次瀏覽某個網站的時候,可能圖片載入速度很慢,但當你重新整理頁面或下次再回來這個網站的時候,速度比第一次來的時候還要快。
這是一種快取的應用。
瀏覽器載入 HTML, JavaScript, CSS, 或圖片檔案的時候,會根據該 Response 的 HTTP Header 上面的資訊,決定這份資料要快取多久。
講人話,就是把那些資源暫時的記在你的裝置上,當你下次點開網頁要瀏覽相同資源的時候,就不用再發送一次 Request 去要資源,直接拿之前存下來的資料來顯示就可以了。
以網頁應用來說,這類型的快取是 Client 端的快取。
因為資訊的內容是存在 Client 端,也就是使用者的裝置上。
伺服器只要在回應的時候打上正確的 HTTP Header 來描述這個 Response 內容的快取時間,用戶端收到後,可以參考這個時間來決定快取的相關動作。
可以打開開發者工具,觀察網路的頁籤中,有沒有資源被標記為 快取
。
(在圖片檔的處理特別明顯。)
上圖以 PChome 首頁為例。
因為快取的內容是記在用戶端,所以使用者也可以自由地把快取清除或停用。
(如上圖右上角的 停用快取
功能)
先不提使用者端的東西,在 Client 端發送一個 Request 後,經過網路的傳輸,中間可能會經過 CDN。
CDN,Content Delivery Network。
想像一個情境,你在台灣,你要瀏覽美國的某個網頁。
距離很遠,網路很慢,中間經過了無數的網路節點,你瀏覽網站的體驗很差。
如果這個時候,在台灣有一台伺服器,他幫你把你想瀏覽的那個網站內容存起來,你要瀏覽網站的時候,那台伺服器就直接把你要看的東西回應給你,而不用跟遠在天邊的那台主機互動,是不是很美好。
那台伺服器就是 CDN Server。
那如果世界上有很多台這樣的伺服器,作為服務提供者,是不是可以讓全世界的人都可以快速看到我提供的內容。
而且如果我自己的伺服器不小心壞掉,或許那些 CDN Server 還可以把內容提供給使用者,讓使用者不會感覺服務中斷。
但要自己假設 CDN 的成本太高了。
(你要怎麼在全世界都有電腦、都有網路、都有維運人員XD)
一般的做法是租用 CDN 的服務,我自己用過覺得滿順手的是 Cloudflare 跟 AWS 的 CloudFront,還有很多其他廠商都有提供 CDN 的服務,這邊不是業配就不多提了。
什麼是 CDN,可以參考這篇 Cloudflare 寫的介紹。
除了使用者端的 Cache、CDN 以外,在伺服器 Server 端也可以做快取。
例如,你的服務會需要跟第三方的服務拿資源,可是那個服務回應的很慢,那你會不會想把這些回應記下來,下次需要用到的時候直接拿之前存下來的東西,而不去跟那個很花時間的第三方服務拿。
或是 Database 的查詢有夠慢,如果你確定要拿的資料很久才會更新,那就可以考慮建立一個快取伺服器 (Cache Server),把東西存起來,並設定一個合理的過期時間 (expire time),在過期前都拿那份資料來用。
這件事可以靠 Redis 來幫我們輕鬆實現。
Redis 代表 Remote Dictionary Server,
是快速的開源記憶體內 (in-memory) 鍵值 (key-value) 資料存放區。
最常見的應用就是做快取。
因為他活在記憶體中,不像大部分的資料庫會相依於硬碟速度,記憶體的速度遠比硬碟快上許多。
Redis 的應用不只做快取,但本篇會拿來當快取伺服器。
其他應用可以參考這篇的 AWS 寫介紹。
要使用 Redis 來進行開發,最方便也最推薦的作法是,起一個 Redis Container。
使用 docker,打開 terminal 敲入:
docker run -d -p 6379:6379 redis
如果使用的是 podman,把
docker run
改為podman run
即可。
Redis 預設的 port 是 6379
。
以上指令沒有跳出錯誤訊息就代表本機的 Redis Service 已經起起來了。
@fastify/redis 是 Fastify 官方維護的與 Redis 進行互動的 Plugin。
可以透過 npm 來安裝:
npm i @fastify/redis
接著就可以透過 server.register()
方法註冊這個 Plugin。
import fastify, { FastifyInstance } from 'fastify'
import fastifyRedis from '@fastify/redis'
const server: FastifyInstance = fastify()
server.register(fastifyRedis, {
url: 'redis://127.0.0.1',
closeClient: true
})
url
帶入 redis 的連線字串。closeClient
設為 true
表示在 app 關閉時中斷連線,預設為 false
。
這個 plugin 會在 FastifyInstance 註冊一個名為 redis
的 decorator。
server.get('/hello', async (request, reply) => {
try {
const myValue = await server.redis.get('my-key')
if (myValue) {
return reply.status(200).send({ message: `Hello ${myValue}` })
}
return reply.status(200).send({ message: 'Cache Miss' })
} catch (error) {
return reply.status(500).send({ error })
}
})
透過 server.redis
拿到 Redis Client 物件,就可以使用 .get()
來拿快取的資料,或透過 .set()
來把資料丟入 Redis Server 中。
上述程式範例使用
async/await
style,也可以用 callback style 來處理,
可以參考官方文件的用法。
本篇介紹了 Cache 在日常中會碰到的點,以及透過 Fastify 官方提供的 Plugin 要怎麼跟 Redis 進行互動。
Cache 是後端非常重要的元件,在系統、架構設計方面就要優先考量的重要存在。
Cache 中定義怎樣的資料才算"髒"的,哪邊應該用,哪邊不應該用,快取資料的 Expire time 要怎麼設定,等等的問題非常多。
曾經有人這麼說過:
共勉之。